package com.yuanfeng.job.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yuanfeng.commoms.constant.CommonType;
import com.yuanfeng.commoms.exception.BizCodeEnume;
import com.yuanfeng.commoms.util.ParseMapUtils;
import com.yuanfeng.commoms.util.ParseTokenUtils;
import com.yuanfeng.commoms.util.ResponseResult;
import com.yuanfeng.commoms.util.querypage.PageUtils;
import com.yuanfeng.commoms.util.querypage.Query;
import com.yuanfeng.commoms.vo.user.UserInfoVO;
import com.yuanfeng.job.dto.JobDTO;
import com.yuanfeng.job.entity.JobPlatformEntity;
import com.yuanfeng.job.job.BaseJob;
import com.yuanfeng.job.mapper.JobPlatformMapper;
import com.yuanfeng.job.service.JobPlatformService;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

/**
 *
 */
@Service
public class JobPlatformServiceImpl extends ServiceImpl<JobPlatformMapper, JobPlatformEntity>
        implements JobPlatformService {

    private static final Logger logger = LoggerFactory.getLogger(JobPlatformServiceImpl.class);

    //加入Qulifier注解，通过名称注入bean
    @Autowired
    @Qualifier("Scheduler")
    private Scheduler scheduler;

    /**
     * 获取某包下所有类
     *
     * @param packageName 包名
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    public static Set<String> getClassName(String packageName, boolean isRecursion) {
        Set<String> classNames = null;
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(".", "/");

        URL url = loader.getResource(packagePath);
        if (url != null) {
            String protocol = url.getProtocol();
            if (protocol.equals("file")) {
                classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion);
            } else if (protocol.equals("jar")) {
                JarFile jarFile = null;
                try {
                    jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                if (jarFile != null) {
                    return getClassNameFromJar(jarFile.entries(), packageName, isRecursion);
                }
            }
        } else {
            /*从所有的jar包中查找包名*/
            classNames = getClassNameFromJars(((URLClassLoader) loader).getURLs(), packageName, isRecursion);
        }
        return classNames;
    }

    /**
     * 从项目文件获取某包下所有类
     *
     * @param filePath    文件路径
     * @param packageName 类名集合
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    private static Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
        Set<String> className = new HashSet<String>();
        File file = new File(filePath);
        File[] files = file.listFiles();
        for (File childFile : files) {
            if (childFile.isDirectory()) {
                if (isRecursion) {
                    className.addAll(getClassNameFromDir(childFile.getPath(), packageName + "." + childFile.getName(), isRecursion));
                }
            } else {
                String fileName = childFile.getName();
                if (fileName.endsWith(".class") && !fileName.contains("$")) {
                    className.add(packageName + "." + fileName.replace(".class", ""));
                }
            }
        }

        return className;
    }

    /**
     * @param jarEntries
     * @param packageName
     * @param isRecursion
     * @return
     */
    private static Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion) {
        Set<String> classNames = new HashSet<String>();

        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            if (!jarEntry.isDirectory()) {
                /*
                 * 这里是为了方便，先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug
                 * (FIXME: 先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug)
                 */
                String entryName = jarEntry.getName().replace("/", ".");
                if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) {
                    entryName = entryName.replace(".class", "");
                    if (isRecursion) {
                        classNames.add(entryName);
                    } else if (!entryName.replace(packageName + ".", "").contains(".")) {
                        classNames.add(entryName);
                    }
                }
            }
        }
        return classNames;
    }

    /**
     * 从所有jar中搜索该包，并获取该包下所有类
     *
     * @param urls        URL集合
     * @param packageName 包路径
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    private static Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
        Set<String> classNames = new HashSet<String>();

        for (int i = 0; i < urls.length; i++) {
            String classPath = urls[i].getPath();

            //不必搜索classes文件夹
            if (classPath.endsWith("classes/")) {
                continue;
            }

            JarFile jarFile = null;
            try {
                jarFile = new JarFile(classPath.substring(classPath.indexOf("/")));
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (jarFile != null) {
                classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion));
            }
        }

        return classNames;
    }

    public static BaseJob getClass(String classname) throws Exception {
        Class<?> class1 = Class.forName(classname);
        return (BaseJob) class1.newInstance();
    }

    @Override
    public ResponseResult queryJob(Map<String, String> paraMap) {
        if (null != paraMap && null != paraMap.get("token")) {
            //校验token
            String token = paraMap.get("token");
            UserInfoVO userInfo = ParseTokenUtils.getPlatformUser(token);
            if (null != userInfo) {
                String packageName = "com.yuanfeng.job.job";
                Map<String, String> map = new HashMap<>();
                Set<String> classNames = getClassName(packageName, false);
                if (classNames != null) {
                    for (String className : classNames) {
                        switch (className) {
                            case "com.yuanfeng.job.job.UpdateActivityGoodsStatus":
                                map.put("更新限时活动", className);
                                break;
                            case "com.yuanfeng.job.job.RetentionMoneyJob":
                                map.put("质保金定时任务",className);
                                break;
                            case "com.yuanfeng.job.job.RetentionMoneyShopJob":
                                map.put("更新质保金基础额度定时任务",className);
                                break;
                            case "com.yuanfeng.job.job.PlusMemberJob":
                                map.put("plus会员更新，红包发放",className);
                                break;
//                          逻辑更改，注释
//                            case "com.yuanfeng.job.job.UpdateSeckillNumStartJob":
//                                map.put("限时秒杀库存为0时，下架商品",className);
//                                break;
                            case "com.yuanfeng.job.job.UpdateSeckillOrderBaseStateJob":
                                map.put("更新限时秒杀超过订单取消时间，未支付订单",className);
                                break;

                            case "com.yuanfeng.job.job.AfterSalesJob":
                                map.put("售后超时未处理定时任务", className);
                                break;
                            case "com.yuanfeng.job.job.SalesPromotionsJob":
                                map.put("代金券修改", className);
                                break;
                            case "com.yuanfeng.job.job.UpdateOdbStatisticsCatJob":
                                map.put("按商品类型每月统计销售额", className);
                                break;
                            case "com.yuanfeng.job.job.DayAndMonthStatisticsJob":
                                map.put("统计每天每月的订单量金额", className);
                                break;

//                            case "com.yuanfeng.job.job.NotCompletedJobimplements":
//                                map.put("统计未完成订单", className);
//                                break;
                            case "com.yuanfeng.job.job.PdbMoneyStatisticsJob":
                                map.put("查询每月激活未充值的总金额", className);
                                break;
                            case "com.yuanfeng.job.job.UpdateReceiverOrderStatusJob":
                                map.put("更新超过7天未确认收货订单", className);
                                break;
                            case "com.yuanfeng.job.job.UpdateCommonStatuJob":
                                map.put("定时改变库存为0的商品状态", className);
                                break;
                            case "com.yuanfeng.job.job.UpdateTimeoutOrderStatusJob":
                                map.put("更新超过1天未支付订单", className);
                                break;
                            case "com.yuanfeng.job.job.AcquiescenceJob":
                                map.put("确认收货超过14天默认好评,不存在退款退货", className);
                                break;
                            case "com.yuanfeng.job.job.RefundOrderHandleJob":
                                map.put("超过48小时售后退款订单商家未处理,自动同意", className);
                                break;
                            case "com.yuanfeng.job.job.ReturnOrderHandleJob":
                                map.put("超过48小时售后退货订单商家未处理,自动同意", className);
                                break;
                            case "com.yuanfeng.job.job.UpdateBusinessOvertimeOrderJob":
                                map.put("待发货退款超24小时商家未处理自动处理退款", className);
                                break;
                            case "com.yuanfeng.job.job.SettleAccountsJob":
                                map.put("结算单", className);
                                break;
                            case "com.yuanfeng.job.job.SettleDealerAccountsJob":
                                map.put("经销订单商家结算",className);
                                break;
                            case "com.yuanfeng.job.job.SettleSupplyAccountsJob":
                                map.put("经销商品供应商结算",className);
                                break;
            				case "com.yuanfeng.job.job.odbStatisticsMonthJob":
            					map.put("统计每月真实交易和自身交易总销售额",className);
            					break;
//					case "com.yuanfeng.job.job.UpdatePointsStateJob":
//						map.put("每日更新积分签到状态",className);
//						break;
                            case "com.yuanfeng.job.job.UpdateEsGoodsWeightJob":
                                map.put("更新ES索引库", className);
                                break;
                            case "com.yuanfeng.job.job.UpdateVirtualOrderTimeJob":
                                map.put("修改虚拟订单的过期时间", className);
                                break;
                            case "com.yuanfeng.job.job.DelOperationDetailJob":
                                map.put("删除用户日志1个月后的日志", className);
                                break;
                            case "com.yuanfeng.job.job.AppJiPushOrderJob":
                                map.put("订单签收推送", className);
                                break;
                            case "com.yuanfeng.job.job.UpdateWxLiveGoodsJob":
                                map.put("更新小程序直播商品状态", className);
                                break;
                            case "com.yuanfeng.job.job.GoodsTimeShelfJob":
                                map.put("商品定时发布上架", className);
                                break;
                            case "com.yuanfeng.job.job.VkOrderCommissionJob":
                                map.put("微客佣金",className);
                                break;
                            case "com.yuanfeng.job.job.UpdateSpinwinActivityStatusJob":
                                map.put("大转盘每日次数更新",className);
                                break;
//                            case "com.yuanfeng.job.job.UpdateAccountStatusJob":
//                                map.put("更改逾期账单状态", className);
//                                break;
                            case "com.yuanfeng.job.job.PromotionJob":
                                map.put("更新会员等级删除的用户等级", className);
                                break;
                        }
                    }
                }
                return ResponseResult.success(map);
            }
            return ResponseResult.fail(BizCodeEnume.TOKEN_IS_ILLEGAL.getCode(), BizCodeEnume.TOKEN_IS_ILLEGAL.getMsg());
        }
        return ResponseResult.fail(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg());
    }

    @Override
    public ResponseResult selectJobAll(JobDTO job) {
        try {
            if (null != job && null != job.getToken()) {
                //校验token
                String token = job.getToken();
                UserInfoVO userInfo = ParseTokenUtils.getPlatformUser(token);
                if (null == userInfo) {
                    userInfo = ParseTokenUtils.getPlatformUser(token);
                }
                if (null != userInfo) {
                    //将对象转换为map
                    Map data = ParseMapUtils.beanToMap(job);

                    //分页查询数据
                    IPage<JobDTO> page = this.baseMapper.selectJobAll(new Query<JobDTO>().getPage(data), job);
                    List<JobDTO> records = page.getRecords();
                    for (JobDTO record : records) {
                        String[] strArray = record.getCronExpression().split(" ");
                        record.setMin(strArray[1]);
                        record.setHour(strArray[2]);
                        record.setDay(strArray[3]);
                        record.setMouth(strArray[4]);
                        record.setWeek(strArray[5]);
                    }
                    //将数据封装进分页参数
                    page.setRecords(records);
                    //返回数据
                    return ResponseResult.success(PageUtils.getPage(page));
                }
                return ResponseResult.fail(BizCodeEnume.TOKEN_IS_ILLEGAL.getCode(), BizCodeEnume.TOKEN_IS_ILLEGAL.getMsg());
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return ResponseResult.fail(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg());

    }

    @Override
    public ResponseResult addJob(JobDTO job) {
        if (null != job && null != job.getToken()) {
            //校验token
            String token = job.getToken();
            UserInfoVO userInfo = ParseTokenUtils.getPlatformUser(token);

            if (null != userInfo) {
                Integer num = this.baseMapper.selectCount(new LambdaQueryWrapper<JobPlatformEntity>()
                        .eq(JobPlatformEntity::getInvokeTarget, job.getInvokeTarget()));
                if (num > 0) {
                    return ResponseResult.fail(BizCodeEnume.JOB_IS_EXIST);
                } else {
                    String cron = "0 " + job.getMin() + " " + job.getHour() + " " + job.getDay() + " " + job.getMouth() + " " + job.getWeek();
                    job.setCronExpression(cron);
                    job.setStatus(CommonType.commonStatus.YES.getCode().toString());
                    this.baseMapper.insertJob(job);
                    try {
                        addJob(job.getInvokeTarget(), job.getJobGroup(), job.getCronExpression());
                    } catch (Exception e) {
                        e.printStackTrace();
                        return ResponseResult.fail(BizCodeEnume.JOB_NO_YET_CLOSED);
                    }
                    return ResponseResult.success(BizCodeEnume.OPERATION_SUCCESSFUL);
                }
            }
            return ResponseResult.fail(BizCodeEnume.TOKEN_IS_ILLEGAL.getCode(), BizCodeEnume.TOKEN_IS_ILLEGAL.getMsg());
        }
        return ResponseResult.fail(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg());

    }

    public void addJob(String jobClassName, String jobGroupName, String cronExpression) throws Exception {

        //定时任务存入redis中
        //RedisUtil.sadd("scheduler", jobClassName);
        //RedisUtil.set(jobClassName, cronExpression);
        // 启动调度器
        scheduler.start();

        //构建job信息
        JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName, jobGroupName).build();

        //表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

        //按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
                .withSchedule(scheduleBuilder).build();

        try {
            scheduler.scheduleJob(jobDetail, trigger);

        } catch (SchedulerException e) {
            System.out.println("创建定时任务失败" + e);
            throw new Exception("创建定时任务失败");
        }
    }

    @Override
    public ResponseResult editJobById(JobDTO job) throws SchedulerException {
        if (null != job && null != job.getToken()) {
            //校验token
            String token = job.getToken();
            UserInfoVO userInfo = ParseTokenUtils.getPlatformUser(token);
            if (null == userInfo) {
                return ResponseResult.fail(BizCodeEnume.TOKEN_IS_ILLEGAL);
            }
            if ("0".equals(job.getStatus())) {
                job.setStatus("1");
                this.baseMapper.editJobById(job);
                scheduler.pauseJob(JobKey.jobKey(job.getInvokeTarget(), job.getJobGroup()));
            }
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String finishTime = dateformat.format(new Date());
            job.setCreateBy(userInfo.getUserRealname());
            job.setCreateTime(finishTime);
            ResponseResult rr = regexFlag(job);
            if (rr.getCode() == 1) {
                String cron = "0 " + job.getMin() + " " + job.getHour() + " " + job.getDay() + " " + job.getMouth() + " " + job.getWeek();
                job.setCronExpression(cron);
                try {
                    deleteJob(job.getInvokeTarget(), job.getJobGroup());
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                    return ResponseResult.fail(BizCodeEnume.UPDATE_ERROR);
                }
            } else {
                return ResponseResult.success();
            }
            int num = this.baseMapper.update(null, new LambdaUpdateWrapper<JobPlatformEntity>()
                    .set(JobPlatformEntity::getStatus, 0)
                    .set(JobPlatformEntity::getRemark, job.getRemark())
                    .set(JobPlatformEntity::getJobName, job.getJobName())
                    .set(JobPlatformEntity::getCronExpression, job.getCronExpression())
                    .set(JobPlatformEntity::getCreateBy, job.getCreateBy())
                    .set(JobPlatformEntity::getCreateTime, job.getCreateTime())
                    .eq(JobPlatformEntity::getJobId, job.getJobId()));
            if (num > 0) {
                try {
                    addJob(job.getInvokeTarget(), job.getJobGroup(), job.getCronExpression());
                } catch (Exception e) {
                    e.printStackTrace();
                    return ResponseResult.fail(BizCodeEnume.UPDATE_ERROR);
                }
                return ResponseResult.success();
            }
            return ResponseResult.fail(BizCodeEnume.TOKEN_IS_ILLEGAL.getCode(), BizCodeEnume.TOKEN_IS_ILLEGAL.getMsg());
        }
        return ResponseResult.fail(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg());

    }

    @Override
    public ResponseResult deleteJobById(JobDTO job) throws Exception {
        if (null != job && null != job.getToken()) {
            //校验token
            String token = job.getToken();
            UserInfoVO userInfo = ParseTokenUtils.getPlatformUser(token);
            if (null == userInfo) {
                return ResponseResult.fail(BizCodeEnume.TOKEN_IS_ILLEGAL);
            }
            this.baseMapper.deleteJobById(job);
            try {
                deleteJob(job.getInvokeTarget(), job.getJobGroup());
            } catch (Exception e) {
                throw new Exception(e.getMessage());
            }
            return ResponseResult.success();
        }
        return ResponseResult.fail(BizCodeEnume.VALID_EXCEPTION.getCode(), BizCodeEnume.VALID_EXCEPTION.getMsg());

    }

    /**
     * 暂停任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult stopJobById(JobDTO job) throws Exception {
        JobPlatformEntity num = this.baseMapper.selectOne(new LambdaQueryWrapper<JobPlatformEntity>()
                .eq(JobPlatformEntity::getInvokeTarget, job.getInvokeTarget()));
        if ("0".equals(num.getStatus())) {
            job.setStatus("1");
            int rows = this.baseMapper.editJobById(job);
            if (rows > 0) {
                scheduler.pauseJob(JobKey.jobKey(job.getInvokeTarget(), job.getJobGroup()));
                logger.info("暂停任务" + job.getInvokeTarget());
            }
        } else {
            try {
                deleteJob(job.getInvokeTarget(), job.getJobGroup());
            } catch (Exception e) {
                throw new Exception(e.getMessage());
            }
            job.setStatus("0");
            int rows = this.baseMapper.editJobById(job);
            if (rows > 0) {
                addJob(job.getInvokeTarget(), job.getJobGroup(), job.getCronExpression());
                logger.info("恢复任务" + job.getInvokeTarget());
            }
        }
        return ResponseResult.success();
    }

    public void deleteJob(String jobClassName, String jobGroupName) throws Exception {
        scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
        scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
        scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
    }

    private ResponseResult regexFlag(JobDTO job) {
        String regex = "^(\\*|0/\\d)$";
        Pattern pattern = Pattern.compile(regex);
        String regexRiQiYu = "^(\\*|0/\\d|\\?)$";
        Pattern patternRiQiYu = Pattern.compile(regexRiQiYu);
        String regexMin = "^([0-9]|[1-5][0-9])$";
        Pattern patternMin = Pattern.compile(regexMin);
        String regexMinHour = "^([0-1]?\\d|2[0-3])$";
        Pattern patternHour = Pattern.compile(regexMinHour);
        String regexDay = "^([0-6])$";
        Pattern patternDay = Pattern.compile(regexDay);
        String regexMouth = "^([1-9]|1[0-1])$";
        Pattern patternMouth = Pattern.compile(regexMouth);
        String regexWeek = "^([1-7]|)$";
        Pattern patternWeek = Pattern.compile(regexWeek);
        if (!job.getDay().contains("?") && !job.getWeek().contains("?")) {
            return ResponseResult.fail(2, null, "日期跟星期需有一者为？");
        }
        if (pattern.matcher(job.getMin()).matches() || patternMin.matcher(job.getMin()).matches()) {
            if (pattern.matcher(job.getHour()).matches() || patternHour.matcher(job.getHour()).matches()) {
                if (patternRiQiYu.matcher(job.getDay()).matches() || patternDay.matcher(job.getDay()).matches()) {
                    if (patternRiQiYu.matcher(job.getMouth()).matches() || patternMouth.matcher(job.getMouth()).matches()) {
                        if (patternRiQiYu.matcher(job.getWeek()).matches() || patternWeek.matcher(job.getWeek()).matches()) {
                            return ResponseResult.success(true);
                        } else {
                            return ResponseResult.fail(2, null, "星期格式有误！");
                        }
                    } else {
                        return ResponseResult.fail(2, null, "月份格式有误！");
                    }
                } else {
                    return ResponseResult.fail(2, null, "日期格式有误！");
                }
            } else {
                return ResponseResult.fail(2, null, "小时格式有误！");
            }
        } else {
            return ResponseResult.fail(2, null, "分钟格式有误！");
        }
    }

}




